#include "internal.h"
#include "Defines.h"
#include "DeviceProperty.h"
#include "PortableDeviceProps.h"
#include "ObjectLock.h"
#include "SensorDdi.h"
#include "Sensor.h"
#include "SensorRegistry.h"
#include "Client.h"
#include "DataQueue.h"
#include "Thread.h"
#include "ClientsManager.h"
#include "ClientsManager.tmh"

//
// single client manager instance
//
CClientsManager CClientsManager::s_ClientsManager;

CClientsManager::CClientsManager(void)
{
	m_ulEventClients = 0;
}

CClientsManager::~CClientsManager(void)
{
	//
	// delete all the client objects
	//
	for(map<IWDFFile*, CClient*>::iterator it = m_mapClients.begin() ;
		it != m_mapClients.end();
		++it)
	{
		delete it->second;
	}

	//
	// stop events processing thread if it is running
	//
	notifyTerminate();
	if(getExitCode() == STILL_ACTIVE)
	{
		if(WaitForSingleObject(getHandle(), 2000) == WAIT_TIMEOUT)
			stop();
	}

	m_mapClients.clear();
}

CClientsManager& CClientsManager::GetClientsManager()
{
	return CClientsManager::s_ClientsManager;
}

bool CClientsManager::Init(ISensorClassExtension *pClassExtension)
{
	CObjectLock lock(this);
	m_spClassExtension = pClassExtension;
	return true;
}

HRESULT CClientsManager::AddClient(IWDFFile *pFile, LPWSTR pwszSensorID)
{
	//
	// validate input
	//
	if(pFile == NULL || pwszSensorID == NULL)
		return E_POINTER;

	CObjectLock lock(this);

	//
	// add this client if not already added
	//
	if(m_mapClients.find(pFile) == m_mapClients.end())
	{
		CClient *pClient = new CClient(pFile);
		m_mapClients[pFile] = pClient;
		return S_OK;
	}

	return HRESULT_FROM_WIN32(ERROR_OBJECT_IN_LIST);
}

HRESULT CClientsManager::RemoveClient(IWDFFile *pFile, LPWSTR pwszSensorID)
{
	//
	// validate input
	//
	if(pFile == NULL || pwszSensorID == NULL)
		return E_POINTER;

	CObjectLock lock(this);

	//
	// remove this client if it exists
	//
	bool removed = false;
	if(m_mapClients.find(pFile) != m_mapClients.end())
	{
		CClient *pClient = m_mapClients[pFile];

		//
		// see if this client registered itself with this
		// sensor
		//
		if(pClient->GetSensor(pwszSensorID) != NULL)
		{
			//
			// remove this sensor from the client's list
			//
			removed = true;
			pClient->RemoveSensor(pwszSensorID);
		}

		//
		// remove the client if no more sensors are left
		//
		if(pClient->GetSensorsCount() == 0)
		{
			m_mapClients.erase(pFile);
			delete pClient;
		}
	}

	return (removed) ? S_OK : HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
}

ULONG CClientsManager::GetClientsCount() const
{
	CObjectLock lock(this);
	return (ULONG)m_mapClients.size();
}

HRESULT CClientsManager::SubscribeToEvents(IWDFFile *pFile, LPWSTR pwszSensorID)
{
	//
	// validate input
	//
	if(pFile == NULL || pwszSensorID == NULL)
		return E_POINTER;

	CObjectLock lock(this);

	//
	// check if this client has registered with us before
	//
	if(m_mapClients.find(pFile) == m_mapClients.end())
		return HRESULT_FROM_WIN32(ERROR_NOT_FOUND);

	CClient *pClient = m_mapClients[pFile];
	bool status = pClient->AddSensor(pwszSensorID);

	//
	// increment count of clients who are listening to us
	//
	if(status)
	{
		++m_ulEventClients;
		if(m_EventsQueue.GetEnabled() == false)
		{
			m_EventsQueue.SetEnabled(true);

			//
			// start the events processing thread
			//
			//this->start();
		}
	}

	return (status) ? S_OK : HRESULT_FROM_WIN32(ERROR_OBJECT_IN_LIST);;
}

HRESULT CClientsManager::UnsubscribeFromEvents(IWDFFile *pFile, LPWSTR pwszSensorID)
{
	//
	// validate input
	//
	if(pFile == NULL || pwszSensorID == NULL)
		return E_POINTER;

	CObjectLock lock(this);

	//
	// check if this client has registered with us before
	//
	if(m_mapClients.find(pFile) == m_mapClients.end())
		return HRESULT_FROM_WIN32(ERROR_NOT_FOUND);

	CClient *pClient = m_mapClients[pFile];
	bool status = pClient->RemoveSensor(pwszSensorID);

	//
	// decrement count of clients who are listening to us
	//
	if(status)
	{
		--m_ulEventClients;
		m_EventsQueue.SetEnabled(m_ulEventClients > 0);

		//if(m_EventsQueue.GetEnabled() == false)
		//{
		//	//
		//	// stop the events processing thread
		//	//
		//	this->notifyTerminate();
		//}
	}

	return (status) ? S_OK : HRESULT_FROM_WIN32(ERROR_OBJECT_NOT_FOUND);;
}

bool CClientsManager::CanRaiseEvents() const
{
	//
	// if the queue is enabled then events are enabled
	//
	return m_EventsQueue.GetEnabled();
}

bool CClientsManager::PostStateChange(std::wstring sensorID, SensorState state)
{
	//
	// raise the state change event
	// NOTE:
	//	We are assuming here that "PostStateChange" will not try
	//	to alter the device ID we are passing to it.  If it does
	//	driver might do weird things.
	//
	if(m_spClassExtension != NULL && CanRaiseEvents())
	{
		m_spClassExtension->PostStateChange(const_cast<LPWSTR>(sensorID.c_str()), state);
		return true;
	}

	return false;
}

bool CClientsManager::PostDataEvent(wstring sensorID, vector<CDeviceProperty>& vData)
{
	if(m_spClassExtension != NULL)
	{
		//
		// enqueue the data; note that if queue is disabled
		// (which it will be if we don't have any clients listening
		// for events) this call will not result in anything
		// actually being enqueued
		//
		//return m_EventsQueue.Enqueue(CSensorEvent(sensorID, vData));
		if(m_EventsQueue.Enqueue(CSensorEvent(sensorID, vData)))
			ProcessEvents();
	}

	return false;
}

bool CClientsManager::PostDataEvent(wstring sensorID, CComPtr<IPortableDeviceValues> spValues)
{
	HRESULT hr = E_FAIL;
	if(m_spClassExtension != NULL)
	{
		CComPtr<IPortableDeviceValuesCollection> spValuesCollection;

		//
		// create and add the values object to a values collection
		//
		hr = spValuesCollection.CoCreateInstance(CLSID_PortableDeviceValuesCollection);
		if (SUCCEEDED(hr))
		{
			hr = spValuesCollection->Add(spValues);
			if (SUCCEEDED(hr))
			{
				//
				// post the event
				//
				hr = m_spClassExtension->PostEvent(const_cast<LPWSTR>(sensorID.c_str()), spValuesCollection);
			}
		}
	}

    return SUCCEEDED(hr);
}

unsigned long CClientsManager::run()
{
	//
	// the objects that this thread will wait
	// on before doing something
	//
	HANDLE hWait[] = {
		m_EventsQueue.GetWaitHandle(),
		hTerminateEvent
	};

	while(true)
	{
		DWORD dwStatus = WaitForMultipleObjects(ARRAY_SIZE(hWait), hWait, FALSE, INFINITE);
		switch(dwStatus)
		{
		case WAIT_OBJECT_0:
			ProcessEvents();
			break;

		case WAIT_FAILED:
		case (WAIT_OBJECT_0 + 1):
			//
			// time to go!
			//
			return 0L;
		}
	}

	return 0L;
}

void CClientsManager::ProcessEvents()
{
	list<CSensorEvent>			events;
	HRESULT						hr;

	//
	// dequeue and process all pending events
	//
	if(m_EventsQueue.DequeueAll(events) == false)
		return;

	for(list<CSensorEvent>::iterator it = events.begin() ; it != events.end() ; ++it)
	{
		CSensorEvent& event = *it;

		//
		// iterate through the properties vector and initialize
		// a "IPortableDeviceValues" object
		//
		CComPtr<IPortableDeviceValues> spValues;
		hr = spValues.CoCreateInstance(CLSID_PortableDeviceValues);
		if(FAILED(hr))
			return;

		for(vector<CDeviceProperty>::const_iterator it = event.props.begin();
			it != event.props.end();
			++it)
		{
			hr = spValues->SetValue(*(it->m_key), &it->m_value);
			if(FAILED(hr))
				return;
		}

		//
		// add a time-stamp to the collection
		//
		SYSTEMTIME st;
		GetSystemTime(&st);

		//
		// convert system time to file time
		//
		FILETIME ft;
		if(SystemTimeToFileTime(&st, &ft) == FALSE)
			return;

		//
		// create a device prop with this and add to the values
		// collection
		//
		CDeviceProperty prop(SENSOR_DATA_TYPE_TIMESTAMP, ft);
		hr = spValues->SetValue(*(prop.m_key), &prop.m_value);
		if(FAILED(hr))
			return;

		//
		// add the data event property key
		//
		hr = spValues->SetGuidValue(SENSOR_EVENT_PARAMETER_EVENT_ID, SENSOR_EVENT_DATA_UPDATED);
		if(FAILED(hr))
			return;

		//
		// post the event
		//
		PostDataEvent(event.sensorID, spValues);
	}
}
